package com.third.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 唐博文
 * @version 2023.1.2
 * @date 2023/10/22 15:52 星期日
 */
@Component
public class JwtUtils {
    
    @Value("${spring.security.jwt.key}")
    String key;
    
    @Value("${spring.security.jwt.expire}")
    int expire;
    
    @Resource
    StringRedisTemplate template;
    //令牌失效
    public boolean invalidateJwt(String headerToken){
        String token=this.convertToken(headerToken);
        if(token==null)return false;
        //验证一下令牌
        Algorithm algorithm=Algorithm.HMAC256(key);
        JWTVerifier jwtVerifier=JWT.require(algorithm).build();
        //将令牌加到redis中
        try {
            //将令牌解析出来
                DecodedJWT jwt=jwtVerifier.verify(token);
                //取出id
            String id = jwt.getId();
            return deleteToken(id,jwt.getExpiresAt());
        }catch (JWTVerificationException e){
            return false;
        }
    }
    
    private boolean deleteToken(String uuid,Date time){
        if(this.isInvalidToken(uuid))return false;
        Date now=new Date();
        long expire = Math.max(time.getTime()-now.getTime(),0);
        template.opsForValue().set(Const.JWT_BLACK_LIST+uuid, "", expire, TimeUnit.MILLISECONDS);
        return true;
    }
    
    //判断是否是一个过期的令牌
    private boolean isInvalidToken(String uuid){
        return Boolean.TRUE.equals(template.hasKey(Const.JWT_BLACK_LIST + uuid));
    }
    
    public DecodedJWT resolveJwt(String headerToken){
        String token=this.convertToken(headerToken);
        if(token==null) return null;
        Algorithm algorithm=Algorithm.HMAC256(key);
        JWTVerifier jwtVerifier=JWT.require(algorithm).build();
        try {
            //用户是否更改过
            DecodedJWT verify=jwtVerifier.verify(token);
            if(this.isInvalidToken(verify.getId()))
                return null;
            //判断token令牌是否过期
            Date expiresAt = verify.getExpiresAt();
            return new Date().after(expiresAt)? null:verify;
        }catch (JWTVerificationException e){
            return null;
        }
    }
    
    //创建jwt令牌
    public String createJwt(UserDetails details,int id,String username){
        Algorithm algorithm=Algorithm.HMAC256(key);
        Date expire=this.expireTime();
        return JWT.create()
                //给令牌添加id
                .withJWTId(UUID.randomUUID().toString())
                .withClaim("id",id)
                .withClaim("name",username)
                .withClaim("authorities",details.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
                //过期时间
                .withExpiresAt(expire)
                //token颁发时间
                .withIssuedAt(new Date())
                .sign(algorithm);
    }
    
    public  Date expireTime(){
        Calendar calendar=Calendar.getInstance();
        calendar.add(Calendar.HOUR,expire*24);
        return calendar.getTime();
    }
    
    private String convertToken(String headerToken){
        if(headerToken==null || !headerToken.startsWith("Bearer"))
            return null;
        return headerToken.substring(7);
    }
    
    //用于解析用户的方法
    public UserDetails toUser(DecodedJWT jwt){
        Map<String, Claim> claims = jwt.getClaims();
        return User
                .withUsername(claims.get("name").asString())
                .password("******")
                .authorities(claims.get("authorities").asArray(String.class))
                .build();
    }
    
    public Integer toId(DecodedJWT jwt){
        Map<String, Claim> claims = jwt.getClaims();
        return claims.get("id").asInt();
    }

}
